package wang.snalc.jpanewdemon.specification_new;


import org.springframework.data.jpa.domain.Specification;

import javax.persistence.criteria.*;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;


public class CommonSpecification<T> implements Specification<T> {

    private final SearchCriteria searchCriteria;

    private final List<Specification<T>> specificationList;

    CommonSpecification(SearchCriteria searchCriteria) {
        this.searchCriteria = searchCriteria;
        this.specificationList = null;
    }

    CommonSpecification(List<Specification<T>> specificationList) {
        this.specificationList = specificationList;
        this.searchCriteria = null;
    }


    private Predicate buildPredicates(SearchCriteria criteria, Root<T> root, CriteriaBuilder criteriaBuilder) {
        Predicate finalPre;
        if (criteria.getSearchCriteriaSet() != null && criteria.getSearchCriteriaSet().size() > 0) {
            List<Predicate> predicateList = new ArrayList<>();
            for (SearchCriteria criteriaSon : criteria.getSearchCriteriaSet()) {
                Predicate predicate = buildPredicates(criteriaSon, root, criteriaBuilder);
                predicateList.add(criteriaBuilder.and(predicate));
            }
            Predicate[] predicates = new Predicate[predicateList.size()];

            if (criteria.getIsOr()) {
                finalPre = criteriaBuilder.or(predicateList.toArray(predicates));
            } else {
                finalPre = criteriaBuilder.and(predicateList.toArray(predicates));
            }
        } else {
            finalPre = buildPredicate(criteria, root, criteriaBuilder);
        }
        return finalPre;
    }

    @Override
    public Predicate toPredicate(Root<T> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
        if (this.searchCriteria == null && this.specificationList != null) {
            List<Predicate> predicateList = new ArrayList<>();
            for (Specification<T> specification : this.specificationList) {
                predicateList.add(specification.toPredicate(root, criteriaQuery, criteriaBuilder));
            }
            return criteriaBuilder.and(predicateList.toArray(new Predicate[0]));
        } else {
            Predicate predicate = buildPredicates(this.searchCriteria, root, criteriaBuilder);
            return criteriaBuilder.and(predicate);
        }


    }

    private Predicate buildPredicate(SearchCriteria criteria, Root<T> root, CriteriaBuilder criteriaBuilder) {
        Join<Object, Object> join = null;
        if (criteria.isJoin()) {
            String[] joins = criteria.getJoinTable();
            if (joins.length > 0) {
                for (int i = 0; i < joins.length; i++) {
                    String joinTable = joins[i];
                    if (i == 0) {
                        join = root.join(joinTable);
                    } else {
                        join = join.join(joinTable);
                    }
                }
            }
        }
        if (criteria.getOperation() == null) {
            return null;
        }
        if (criteria.getOperation().equalsIgnoreCase(">=")) {
            if ((Objects.requireNonNull(criteria.isJoin() ? join : root)).get(criteria.getKey()).getJavaType() == LocalDateTime.class) {
                return criteriaBuilder.greaterThanOrEqualTo((Objects.requireNonNull(criteria.isJoin() ? join : root)).get(criteria.getKey()), (LocalDateTime) criteria.getValue());
            } else if ((Objects.requireNonNull(criteria.isJoin() ? join : root)).get(criteria.getKey()).getJavaType() == LocalDate.class) {
                return criteriaBuilder.greaterThanOrEqualTo((Objects.requireNonNull(criteria.isJoin() ? join : root)).get(criteria.getKey()), (LocalDate) criteria.getValue());
            } else {
                return criteriaBuilder.greaterThanOrEqualTo((Objects.requireNonNull(criteria.isJoin() ? join : root)).get(criteria.getKey()), criteria.getValue().toString());
            }
        } else if (criteria.getOperation().equalsIgnoreCase("<=")) {
            if ((Objects.requireNonNull(criteria.isJoin() ? join : root)).get(criteria.getKey()).getJavaType() == LocalDateTime.class) {
                return criteriaBuilder.lessThanOrEqualTo((Objects.requireNonNull(criteria.isJoin() ? join : root)).get(criteria.getKey()), (LocalDateTime) criteria.getValue());
            } else if ((Objects.requireNonNull(criteria.isJoin() ? join : root)).get(criteria.getKey()).getJavaType() == LocalDate.class) {
                return criteriaBuilder.lessThanOrEqualTo((Objects.requireNonNull(criteria.isJoin() ? join : root)).get(criteria.getKey()), (LocalDate) criteria.getValue());
            } else {
                return criteriaBuilder.lessThanOrEqualTo((Objects.requireNonNull(criteria.isJoin() ? join : root)).get(criteria.getKey()), criteria.getValue().toString());
            }
        } else if (criteria.getOperation().equalsIgnoreCase("like")) {
            if (criteria.getValue().getClass() == String.class) {
                return criteriaBuilder.like((Objects.requireNonNull(criteria.isJoin() ? join : root)).get(criteria.getKey()), "%" + criteria.getValue() + "%");
            } else {
                return criteriaBuilder.equal((Objects.requireNonNull(criteria.isJoin() ? join : root)).get(criteria.getKey()), criteria.getValue());
            }
        } else if (criteria.getOperation().equalsIgnoreCase("notLike")) {
            if (criteria.getValue().getClass() == String.class) {
                return criteriaBuilder.notLike((Objects.requireNonNull(criteria.isJoin() ? join : root)).get(criteria.getKey()), "%" + criteria.getValue() + "%");
            } else {
                return criteriaBuilder.notEqual((Objects.requireNonNull(criteria.isJoin() ? join : root)).get(criteria.getKey()), criteria.getValue());
            }
        } else if (criteria.getOperation().equalsIgnoreCase("=")) {
            if (criteria.getValue() == null) {
                return criteriaBuilder.isNull((Objects.requireNonNull(criteria.isJoin() ? join : root)).get(criteria.getKey()));
            } else {
                if (criteria.getValue().getClass() == String.class) {
                    return criteriaBuilder.equal((Objects.requireNonNull(criteria.isJoin() ? join : root)).<String>get(criteria.getKey()), criteria.getValue());
                } else {
                    return criteriaBuilder.equal((Objects.requireNonNull(criteria.isJoin() ? join : root)).get(criteria.getKey()), criteria.getValue());
                }
            }
        } else if (criteria.getOperation().equalsIgnoreCase("!=")) {
            if (criteria.getValue() == null) {
                return criteriaBuilder.isNotNull((Objects.requireNonNull(criteria.isJoin() ? join : root)).get(criteria.getKey()));
            } else {
                if (criteria.getValue().getClass() == String.class) {
                    return criteriaBuilder.notEqual((Objects.requireNonNull(criteria.isJoin() ? join : root)).<String>get(criteria.getKey()), criteria.getValue());
                } else {
                    return criteriaBuilder.notEqual((Objects.requireNonNull(criteria.isJoin() ? join : root)).get(criteria.getKey()), criteria.getValue());
                }
            }
        }
        return null;
    }
}
